/*
 * Copyright (c) 2009 Robert Esser
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.e2ser.petri;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

import net.e2ser.PNBrowser;
import net.e2ser.component.AbstractComponent;
import net.e2ser.component.OneD;
import net.e2ser.component.Place;
import net.e2ser.component.RootNode;
import net.e2ser.component.Transition;
import net.e2ser.util.XMLReader;
import net.e2ser.util.XMLWriter;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

/**
 * This class represents the Petri Net view and controller
 * It knows how to display a net and how to act upon actions
 * 
 * @author Rob Esser
 * @version 8/8/2009
 * 
 */
public class Editor extends View implements Runnable {

  // SHOULD USE enum here
  public static final int STOP = 0;
  public static final int STEP = 1;
  public static final int FORWARD = 2;
  public static final int FASTFORWARD = 3;
  
  public static final int TextOffsetX = 0;
  public static final int TextOffsetY = 2;

  private static final int assumedScreenWidth = 160;
  private static final int assumedScreenHeight = 144;
  private static final String PHILOSOPHER = "philosopher.xml";
  private static final String FORKJOIN = "fork_join.xml";
  private static final String MUTEX = "mutual_exclusion.xml";

  private EditorMode mode = EditorMode.selectMode(); 
  private AbstractComponent toInsert;
  
  /** the current selection */
  private ArrayList<AbstractComponent> pick = new ArrayList<AbstractComponent>();
  
  private int oldX = 0, oldY = 0;
  private boolean redrawAll = true;
  private AtomicInteger running = new AtomicInteger(STOP);
  private RootNode displayedNode = new RootNode(this);
  private Thread simulator_thread = new Thread(this);
  private TextView statusView = null;
  private String statusText;

  // a display scaling factor for nets
  public static double DisplayScale = 1.0;

  public Editor(Context context, AttributeSet attr) {
    super(context, attr);
    // reduce simulator priority to give user interface a chance
    simulator_thread.setPriority(Thread.NORM_PRIORITY - 1);
    simulator_thread.start();
    //remember if we have displayed anything
    boolean showFileDone = false;

    //check that default files are saved in default directory
    if (!fileExists(PHILOSOPHER)) {
      showPhilosopher();
      saveFile(PHILOSOPHER);
    }
    if (!fileExists(FORKJOIN)) {
      showForkJoin();
      saveFile(FORKJOIN);
    }
    if (!fileExists(MUTEX)) {
      showMutex();
      saveFile(MUTEX);
      showFileDone = true;
    }
    //if temp file exists the load that
    if (fileExists(PNBrowser.TEMP_FILE_NAME)) {
      loadFile(PNBrowser.TEMP_FILE_NAME);
      showFileDone = true;
    }
    if (!showFileDone) {
      showMutex();
    }
  }

  /** does the file exist in the default directory? */
  private boolean fileExists(String fileName) {
    String[] files = this.getContext().fileList();
    for (int i = 0; i < files.length; i++) {
      if (files[i].equals(fileName)) {
        return true;
      }
    }
    return false;
  }

  /**
   * remember the type of node to insert and place the editor in insert mode
   */
  public void toInsert(AbstractComponent node) {
    showStatus("");
    // set the next element to insert
    if (node == null) {
      mode = EditorMode.selectMode();
      toInsert = null;
    } else {
      mode = EditorMode.insertMode();
      toInsert = node;
    }
  }

  public void toInsertArc() {
    showStatus("");
    // set the next element to insert
    mode = EditorMode.connectMode();
    toInsert = null;
    deselect();
  }

  /**
   * Attempt to add a token for all selected elements
   */
  public void insertTokens() {
    // insert tokens into the selected elements
    int num = pick.size();
    for (int i = 0; i < num; i++) {
      (pick.get(i)).addToken();
    }
  }

  /**
   * Attempt to remove a token for all selected elements
   */
  public void deleteTokens() {
    // insert tokens into the selected elements
    int num = pick.size();
    for (int i = 0; i < num; i++) {
      (pick.get(i)).removeToken();
    }
  }

  /**
   *  deselect the current selection
   */
  public void deselect() {
    ArrayList<AbstractComponent> oldPick = new ArrayList<AbstractComponent>();
    oldPick.addAll(pick);
    pick = new ArrayList<AbstractComponent>();
    int numShapes = oldPick.size();
    for (int i = 0; i < numShapes; i++) {
      AbstractComponent s = oldPick.get(i);
      invalidate(s.bounds());
    }
  }

  public void stop() {
    running.set(STOP);
  }

  public void step() {
    running.set(STEP);
  }

  public void forward() {
    running.set(FORWARD);
  }

  public void fastForward() {
    running.set(FASTFORWARD);
  }

  public AbstractComponent elementAt(int x, int y) {
    // return the closest element containing x,y
    double bestdist = Double.MAX_VALUE;
    AbstractComponent s, newPick = null;
    if (displayedNode != null) {
      int numShapes = displayedNode.size();
      for (int i = 0; i < numShapes; i++) {
        s = (AbstractComponent) displayedNode.elementAt(i);
        if (s.contains(x, y)) {
          double dist = (s.center().x - x) * (s.center().x - x) + (s.center().y - y) * (s.center().y - y);
          if (dist < bestdist) {
            newPick = s;
            bestdist = dist;
          }
        }
      }
    }
    return newPick;
  }

  /** dispatcher for touch events */
  public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
      pointerPressed((int) event.getX(), (int) event.getY());
      return true;
    } else if (event.getAction() == MotionEvent.ACTION_UP) {
      pointerReleased((int) event.getX(), (int) event.getY());
      return true;
    } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
      pointerDragged((int) event.getX(), (int) event.getY());
      return true;
    } 
    return super.onTouchEvent(event);
  }

  /**
   * This is called during layout when the size of this view has changed. If
   * you were just added to the view hierarchy, you're called with the old
   * values of 0.
   * 
   * @param w Current width of this view.
   * @param h Current height of this view.
   * @param oldw Old width of this view.
   * @param oldh Old height of this view.
   */
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    Log.v(PNBrowser.TAG, "Editor.onSizeChanged: " + w + ", " + h + " old: " + oldw + ", " + oldh);
    if (w > 100 && h > 100) {
      //resize the net
      DisplayScale = Math.min(((double) w) / assumedScreenWidth, ((double) h) / assumedScreenHeight);
      Log.v(PNBrowser.TAG, "Editor DisplayScale: " + DisplayScale);
      resizeNet(DisplayScale);
    }
  }

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    Log.v(PNBrowser.TAG, "Editor.onMeasure: " + widthMeasureSpec + ", " + heightMeasureSpec);
}

  /**
   * Handle a drag
   */
  private void pointerDragged(int x, int y) {
    if (mode.isSelectMode() && ((x != oldX) || (y != oldY))) {
      int numShapes = pick.size();
      Rect rect = null;
      for (int i = 0; i < numShapes; i++) {
        AbstractComponent node = pick.get(i);
        if (rect == null) {
          rect = new Rect(node.bounds());
        } else {
          rect.union(node.bounds());
        }
        node.move(node.origin().x + x - oldX, node.origin().y + y - oldY);
        rect.union(node.parent.neighboursBoundsOf(node));
        showStatus("" + node.bounds().left + ", " + node.bounds().top);
      }
      if (rect != null) {
        rect.inset(-1, -1);
        invalidate(rect);
      }
      oldX = x;
      oldY = y;
    }
  }

  /**
   * handle a press
   */
  private void pointerPressed(int x, int y) {
    if (mode.isSelectMode()) {
      AbstractComponent newPick = elementAt(x, y);
      if (newPick != null) {
        if (pick.contains(newPick)) {
          // pick.remove(newPick);
          // invalidate(newPick.bounds());
        } else {
          pick.add(newPick);
          invalidate(newPick.bounds());
        }
      } else {
        deselect();
      }
      oldX = x;
      oldY = y;
    } else if (mode.isDeleteMode()) {
      AbstractComponent comp = elementAt(x, y);
      if (comp != null) {
        displayedNode.deleteElement(comp);
        for (int i = pick.size() - 1; i >= 0; i--) {
          pick.remove(i);
        }
        invalidate();
      }
      oldX = x;
      oldY = y;
    } else if (mode.isInsertMode()) {
      int dimX = toInsert.componentWidth();
      int dimY = toInsert.componentHeight();
      AbstractComponent node = toInsert.insertElementIn(displayedNode, x - dimX / 2, y - dimY / 2, DisplayScale);
      if (node != null) {
        Rect rect = node.bounds();
        invalidate(rect);
      }
    } else if (mode.isConnectMode()) {
      showStatus("");
      AbstractComponent node = elementAt(x, y);
      if (node != null) {
        if (pick.size() == 0) {
          if (node.canAddConnection(node)) {
            pick.add(node);
            Rect rect = node.bounds();
            invalidate(rect);
          } else {
            /** can't connect to this element */
            showStatus("Can't connect to this element");
          }
        } else {
          if (node.canAddConnection((pick.get(0)), node)) {
            displayedNode.insertDeleteArc(pick.get(0), node);
            deselect();
            invalidate();
          } else {
            /* can't connect these elements */
            showStatus("Can't connect these elements");
            deselect();
          }
        }
      }
    }
  }

  /**
   * handle a release
   */
  private void pointerReleased(int x, int y) {
    if (mode.isSelectMode() && ((x != oldX) || (y != oldY))) {
      int numShapes = pick.size();
      for (int i = 0; i < numShapes; i++) {
        AbstractComponent node = pick.get(i);
        node.move(node.origin().x + x - oldX, node.origin().y + y - oldY);
      }
      oldX = x;
      oldY = y;
    }
    if (mode.isSelectMode()) {
      // see if we are moving an object then save coordinate
      if (pick.size() != 0) {
        AbstractComponent node = pick.get(0);
        node.move(node.origin().x + x - oldX, node.origin().y + y - oldY);
      }
      if (!redrawAll) {
        redrawAll = true;
        invalidate();
      }
    } else {
    }
  }

  public void delete() {
    // go into delete mode
    mode = EditorMode.deleteMode();
  }

  public boolean onKeyDown(int keycode, KeyEvent event) {
    // //Log.v(PNBrowser.TAG, "event down: " + event.toString());
    switch (keycode) {
    case KeyEvent.KEYCODE_DPAD_DOWN:
      return true;
    case KeyEvent.KEYCODE_DPAD_UP:
      return true;
    case KeyEvent.KEYCODE_DPAD_LEFT:
      return true;
    case KeyEvent.KEYCODE_DPAD_RIGHT:
      return true;
    case KeyEvent.KEYCODE_DPAD_CENTER:
      displayedNode.deleteElements(pick);
      for (int i = pick.size() - 1; i >= 0; i--) {
        pick.remove(i);
      }
      invalidate();
      return true;
    }
    return super.onKeyDown(keycode, event);
  }

  private void resizeNet(double scale) {
    // inform all the components of the new size
    AbstractComponent s;
    int numShapes = displayedNode.size();
    for (int i = 0; i < numShapes; i++) {
      s = displayedNode.elementAt(i);
      s.resize(scale);
    }
    OneD a;
    numShapes = displayedNode.connectorsSize();
    for (int i = 0; i < numShapes; i++) {
      a = (OneD) displayedNode.connectionElementAt(i);
      a.resize(scale);
    }
  }
  
  protected void onDraw(Canvas canvas) {
    // clear the background
    canvas.drawColor(Color.WHITE);
    int numShapes;
    if (displayedNode != null) {
      if (redrawAll) {
        OneD a;
        numShapes = displayedNode.connectorsSize();
        for (int i = 0; i < numShapes; i++) {
          a = (OneD) displayedNode.connectionElementAt(i);
          a.draw(canvas);
        }
      }
      AbstractComponent s;
      numShapes = displayedNode.size();
      for (int i = 0; i < numShapes; i++) {
        s = displayedNode.elementAt(i);
        s.draw(canvas);
        if (pick.contains(s)) {
          s.drawSelected(canvas);
        }
      }
    }
  }

  // This is the body of the thread--the method that does the animation.
  public void run() {
    while (true) {
      switch (running.get()) {
      case STOP:
        while (running.get() == STOP) {
          try {
            Thread.sleep(500);
          } catch (InterruptedException ex) {
          }
        }
        break;
      case STEP:
        doStepForward();
        if (running.get() == STEP) {
          running.set(STOP);
        }
        break;
      case FORWARD:
        doForward();
        if (running.get() == FORWARD) {
          running.set(STOP);
        }
        break;
      case FASTFORWARD:
        doFastForward();
        if (running.get() == FASTFORWARD) {
          running.set(STOP);
        }
        break;
      default:
        //should never get here!
        try {
          Thread.sleep(500);
        } catch (InterruptedException ex) {
        }
      }
    }
  }

  private SimulatorMode getCurrentSimulatorMode() {
    // read the sim type choice and convert into a mode
    // return SimulatorMode.ceMode();
    return SimulatorMode.ptMode();
  }

  public void doStepForward() {
    // the step button has been pressed
    showStatus("");
    if ((displayedNode.topNode().step(getCurrentSimulatorMode(), true, 500)) == 0) {
      showStatus("The net is dead");
    }
  }

  public void doForward() {
    // the forward button has been pressed
    boolean cont = true;
    showStatus("");
    while ((running.get() == FORWARD) && cont) {
      // the number of ms to wait while firing
      int delay = 750;
      if ((displayedNode.topNode().step(getCurrentSimulatorMode(), true, delay)) == 0) {
        showStatus("The net is dead");
        cont = false;
      }
    }
  }

  public void doFastForward() {
    // the forward button has been pressed
    long startTime, endTime;
    boolean cont = true;
    int totalFirings = 0;
    showStatus("Simulator Running");
    startTime = System.currentTimeMillis();
    while ((running.get() == FASTFORWARD) && cont) {
      if ((displayedNode.topNode().step(getCurrentSimulatorMode(), false, 0)) == 0) {
      //if ((displayedNode.topNode().step(getCurrentSimulatorMode(), true, 100)) == 0) {
        showStatus("The net is dead");
        cont = false;
      } else {
        totalFirings++;
      }
    }
    if (cont) {
      endTime = System.currentTimeMillis();
      if (startTime == endTime) {
        showStatus(totalFirings + " firings, ");
      } else {
        showStatus(totalFirings + " firings in " + (endTime - startTime) + "ms " + "= " + ((totalFirings * 1000) / (endTime - startTime)) + " firings/s");
      }
    }
    postInvalidate();
  }
  
  public void setSatusView(TextView statusView) {
    this.statusView  = statusView;
  }

  /**
   * showStatus may be called in any thread so use the handler mechanism
   * @param string
   */
  public void showStatus(String string) {
    if (statusView != null) {
      statusText = string;
      //get the handler to output the message
      handler.sendEmptyMessage(0);
    }
  }
  
  private Handler handler = new Handler() {
    public void handleMessage(Message msg) {
      statusView.setText(statusText);
    }
  };

  /**
   * Read a file from the file system and load it into the browser
   * @param fileName
   */
  public void loadFile(String fileName) {
    Log.v(PNBrowser.TAG, "Editor.loadFile() called: " + fileName);
    showStatus("Opening " + fileName);
    try {
      double oldScale = DisplayScale;
      DisplayScale = 1.0;
      InputStream is = this.getContext().openFileInput(fileName);
      XMLReader reader = new XMLReader(is);
      RootNode top = reader.parse();
      reader.close();
      //remove reference to editor
      displayedNode.editor(null);
      top.editor(this);
      displayedNode = top;
      DisplayScale = oldScale;
      resizeNet(DisplayScale);
      invalidate();
    } catch (FileNotFoundException e) {
      Log.v(PNBrowser.TAG, "Editor.loadFile() Exception: " + e.getMessage());
      e.printStackTrace();
    } catch (IOException e) {
      Log.v(PNBrowser.TAG, "Editor.loadFile() Exception: " + e.getMessage());
      e.printStackTrace();
    } catch (Exception e) {
      Log.v(PNBrowser.TAG, "Editor.saveFile() Exception: " + e.getMessage());
      e.printStackTrace();
    }
    showStatus("");
  }

  /**
   * Store a net in the file system
   * @param fileName
   */
  public void saveFile(String fileName) {
    Log.v(PNBrowser.TAG, "Editor.saveFile() called: " + fileName);
    showStatus("Saving " + fileName);
    double oldScale = DisplayScale;
    try {
      resizeNet(1.0);
      OutputStream os = this.getContext().openFileOutput(fileName, Context.MODE_WORLD_READABLE);
      XMLWriter writer = new XMLWriter(os);
      writer.writeHeader();
      displayedNode.toXML(writer);
      writer.close();
    } catch (FileNotFoundException e) {
      Log.v(PNBrowser.TAG, "Editor.saveFile() FileNotFoundException: " + e.getMessage());
      e.printStackTrace();
    } catch (IOException e) {
      Log.v(PNBrowser.TAG, "Editor.saveFile() IOException: " + e.getMessage());
      e.printStackTrace();
    } catch (Exception e) {
      Log.v(PNBrowser.TAG, "Editor.saveFile() Exception: " + e.getMessage());
      e.printStackTrace();
    }
    DisplayScale = oldScale;
    resizeNet(DisplayScale);
    showStatus("");
  }

  /**
   * A hard-coded example net
   */
  private void showForkJoin() {
    RootNode top = new RootNode(this);

    int extent = 20;

    Transition t1 = new Transition(top, "t1", 10, 25, extent, extent, DisplayScale);
    Transition t2 = new Transition(top, "t2", 70, 10, extent, extent, DisplayScale);
    Transition t3 = new Transition(top, "t3", 70, 40, extent, extent, DisplayScale);
    Transition t4 = new Transition(top, "t4", 130, 25, extent, extent, DisplayScale);
    Transition t5 = new Transition(top, "t5", 40, 85, extent, extent, DisplayScale);
    Transition t6 = new Transition(top, "t6", 40, 115, extent, extent, DisplayScale);
    Transition t7 = new Transition(top, "t7", 100, 85, extent, extent, DisplayScale);
    Transition t8 = new Transition(top, "t8", 100, 115, extent, extent, DisplayScale);

    Place p1 = new Place(top, "p1", 40, 10, extent, extent, DisplayScale);
    Place p2 = new Place(top, "p2", 40, 40, extent, extent, DisplayScale);
    Place p3 = new Place(top, "p3", 100, 10, extent, extent, DisplayScale);
    Place p4 = new Place(top, "p4", 100, 40, extent, extent, DisplayScale);
    Place p5 = new Place(top, "p5", 10, 100, extent, extent, DisplayScale);
    p5.addToken(false);
    Place p6 = new Place(top, "p6", 70, 85, extent, extent, DisplayScale);
    Place p7 = new Place(top, "p7", 70, 115, extent, extent, DisplayScale);
    Place p8 = new Place(top, "p8", 130, 100, extent, extent, DisplayScale);

    top.insertElement(t1);
    top.insertElement(t2);
    top.insertElement(t3);
    top.insertElement(t4);
    top.insertElement(t5);
    top.insertElement(t6);
    top.insertElement(t7);
    top.insertElement(t8);

    top.insertElement(p1);
    top.insertElement(p2);
    top.insertElement(p3);
    top.insertElement(p4);
    top.insertElement(p5);
    top.insertElement(p6);
    top.insertElement(p7);
    top.insertElement(p8);

    top.insertDeleteArc(t1, p1);
    top.insertDeleteArc(t1, p2);
    top.insertDeleteArc(p1, t2);
    top.insertDeleteArc(p2, t3);
    top.insertDeleteArc(t2, p3);
    top.insertDeleteArc(t3, p4);
    top.insertDeleteArc(p3, t4);
    top.insertDeleteArc(p4, t4);
    top.insertDeleteArc(t4, p8);
    top.insertDeleteArc(p8, t7);
    top.insertDeleteArc(p8, t8);
    top.insertDeleteArc(t7, p6);
    top.insertDeleteArc(t8, p7);
    top.insertDeleteArc(p6, t5);
    top.insertDeleteArc(p7, t6);
    top.insertDeleteArc(t5, p5);
    top.insertDeleteArc(t6, p5);
    top.insertDeleteArc(p5, t1);
    displayedNode = top;
    invalidate();
  }

  /**
   * A hard-coded example net
   */
  private void showMutex() {
    RootNode top = new RootNode(this);
    int extent = 20;

    Transition t1 = new Transition(top, "t1", 5, 65, extent, extent, DisplayScale);
    Transition into1 = new Transition(top, "in1", 40, 5, extent, extent, DisplayScale);
    Transition ret1 = new Transition(top, "rt1", 40, 115, extent, extent, DisplayScale);
    Transition into2 = new Transition(top, "in2", 95, 5, extent, extent, DisplayScale);
    Transition ret2 = new Transition(top, "rt2", 95, 115, extent, extent, DisplayScale);
    Transition t2 = new Transition(top, "t2", 135, 65, extent, extent, DisplayScale);

    top.insertElement(t1);
    top.insertElement(into1);
    top.insertElement(ret1);
    top.insertElement(into2);
    top.insertElement(ret2);
    top.insertElement(t2);

    Place wait1 = new Place(top, "wt1", 5, 5, extent, extent, DisplayScale);
    wait1.addToken(false);
    Place finish1 = new Place(top, "fin1", 5, 115, extent, extent, DisplayScale);
    Place critical1 = new Place(top, "crit1", 40, 65, extent, extent, DisplayScale);
    Place semaphore = new Place(top, "sem", 67, 65, extent, extent, DisplayScale);
    semaphore.addToken(false);
    Place critical2 = new Place(top, "crit2", 95, 65, extent, extent, DisplayScale);
    Place wait2 = new Place(top, "wt2", 135, 5, extent, extent, DisplayScale);
    wait2.addToken(false);
    Place finish2 = new Place(top, "fin2", 135, 115, extent, extent, DisplayScale);

    top.insertElement(wait1);
    top.insertElement(finish1);
    top.insertElement(critical1);
    top.insertElement(semaphore);
    top.insertElement(critical2);
    top.insertElement(wait2);
    top.insertElement(finish2);

    top.insertDeleteArc(into1, critical1);
    top.insertDeleteArc(critical1, ret1);
    top.insertDeleteArc(ret1, finish1);
    top.insertDeleteArc(finish1, t1);
    top.insertDeleteArc(t1, wait1);
    top.insertDeleteArc(wait1, into1);
    top.insertDeleteArc(semaphore, into1);
    top.insertDeleteArc(ret1, semaphore);
    top.insertDeleteArc(into2, critical2);
    top.insertDeleteArc(critical2, ret2);
    top.insertDeleteArc(ret2, finish2);
    top.insertDeleteArc(finish2, t2);
    top.insertDeleteArc(t2, wait2);
    top.insertDeleteArc(wait2, into2);
    top.insertDeleteArc(semaphore, into2);
    top.insertDeleteArc(ret2, semaphore);
    displayedNode = top;
    invalidate();
  }

  /**
   * A hard-coded example net
   */
  private void showPhilosopher() {
    RootNode top = new RootNode(this);
    displayedNode = top;
    int extent10 = 10;
    int extent20 = 20;

    Transition hun1 = new Transition(top, "hun1", 30, 10, extent10, extent10, DisplayScale);
    Transition hun2 = new Transition(top, "hun2", 15, 95, extent10, extent10, DisplayScale);
    Transition hun3 = new Transition(top, "hun3", 80, 132, extent10, extent10, DisplayScale);
    Transition hun4 = new Transition(top, "hun4", 142, 100, extent10, extent10, DisplayScale);
    Transition hun5 = new Transition(top, "hun5", 120, 10, extent10, extent10, DisplayScale);
    Transition fin1 = new Transition(top, "fin1", 65, 45, extent10, extent10, DisplayScale);
    Transition fin2 = new Transition(top, "fin2", 55, 70, extent10, extent10, DisplayScale);
    Transition fin3 = new Transition(top, "fin3", 80, 90, extent10, extent10, DisplayScale);
    Transition fin4 = new Transition(top, "fin4", 105, 70, extent10, extent10, DisplayScale);
    Transition fin5 = new Transition(top, "fin5", 90, 45, extent10, extent10, DisplayScale);

    top.insertElement(hun1);
    top.insertElement(hun2);
    top.insertElement(hun3);
    top.insertElement(hun4);
    top.insertElement(hun5);
    top.insertElement(fin1);
    top.insertElement(fin2);
    top.insertElement(fin3);
    top.insertElement(fin4);
    top.insertElement(fin5);

    Place fork1 = new Place(top, "frk1", 75, 5, extent20, extent20, DisplayScale);
    Place fork2 = new Place(top, "frk2", 20, 45, extent20, extent20, DisplayScale);
    Place fork3 = new Place(top, "frk3", 42, 108, extent20, extent20, DisplayScale);
    Place fork4 = new Place(top, "frk4", 105, 108, extent20, extent20, DisplayScale);
    Place fork5 = new Place(top, "frk5", 125, 50, extent20, extent20, DisplayScale);
    fork1.addToken(false);
    fork2.addToken(false);
    fork3.addToken(false);
    fork4.addToken(false);
    fork5.addToken(false);
    Place eat1 = new Place(top, "eat1", 45, 25, extent20, extent20, DisplayScale);
    Place eat2 = new Place(top, "eat2", 30, 77, extent20, extent20, DisplayScale);
    Place eat3 = new Place(top, "eat3", 75, 105, extent20, extent20, DisplayScale);
    Place eat4 = new Place(top, "eat4", 115, 80, extent20, extent20, DisplayScale);
    Place eat5 = new Place(top, "eat5", 100, 25, extent20, extent20, DisplayScale);

    top.insertElement(fork1);
    top.insertElement(fork2);
    top.insertElement(fork3);
    top.insertElement(fork4);
    top.insertElement(fork5);
    top.insertElement(eat1);
    top.insertElement(eat2);
    top.insertElement(eat3);
    top.insertElement(eat4);
    top.insertElement(eat5);

    top.insertDeleteArc(fork1, hun1);
    top.insertDeleteArc(fork2, hun1);
    top.insertDeleteArc(fork2, hun2);
    top.insertDeleteArc(fork3, hun2);
    top.insertDeleteArc(fork3, hun3);
    top.insertDeleteArc(fork4, hun3);
    top.insertDeleteArc(fork4, hun4);
    top.insertDeleteArc(fork5, hun4);
    top.insertDeleteArc(fork5, hun5);
    top.insertDeleteArc(fork1, hun5);

    top.insertDeleteArc(hun1, eat1);
    top.insertDeleteArc(hun2, eat2);
    top.insertDeleteArc(hun3, eat3);
    top.insertDeleteArc(hun4, eat4);
    top.insertDeleteArc(hun5, eat5);
    top.insertDeleteArc(eat1, fin1);
    top.insertDeleteArc(eat2, fin2);
    top.insertDeleteArc(eat3, fin3);
    top.insertDeleteArc(eat4, fin4);
    top.insertDeleteArc(eat5, fin5);

    top.insertDeleteArc(fin1, fork1);
    top.insertDeleteArc(fin1, fork2);
    top.insertDeleteArc(fin2, fork2);
    top.insertDeleteArc(fin2, fork3);
    top.insertDeleteArc(fin3, fork3);
    top.insertDeleteArc(fin3, fork4);
    top.insertDeleteArc(fin4, fork4);
    top.insertDeleteArc(fin4, fork5);
    top.insertDeleteArc(fin5, fork5);
    top.insertDeleteArc(fin5, fork1);
    invalidate();
  }

}
